package com.danguo.happymonkey;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import android.opengl.GLES20;
import android.opengl.Matrix;
import android.os.SystemClock;

public class Triangles
{
   /** How many bytes per float. */
   private final int mBytesPerFloat = 4;

   /** How many elements per vertex. */
   private final int mStrideBytes = 7 * mBytesPerFloat;

   /** Offset of the position data. */
   private final int mPositionOffset = 0;

   /** Size of the position data in elements. */
   private final int mPositionDataSize = 3;

   /** Offset of the color data. */
   private final int mColorOffset = 3;

   /** Size of the color data in elements. */
   private final int mColorDataSize = 4;   
   
   private int theProgramHandle;
   private int theMVPMatrixHandle;
   private int thePositionHandle;
   private int theColorHandle;
   private float[] theViewMatrix;
   private float[] theProjectionMatrix;
   private Triangle[] theTriangles = new Triangle[3];

   private float[] mModelMatrix = new float[16];

   Triangles()
   {
      final String vertexShader = 
            "uniform mat4 u_MVPMatrix; " + // A constant
            "attribute vec4 a_Position; " + // Per-vertex position info we will pass in.
            "attribute vec4 a_Color; " + // Per-vertex color information we will pass in.
            "varying vec4 v_Color; " + // This will be passed into the fragment shader.
            "void main() " + // The entry point for our vertex shader.
            "{ " + 
               "v_Color = a_Color; " + // Pass the color through to the fragment shader.
                                       // It will be interpolated across the triangle.
               "gl_Position = u_MVPMatrix" +
               "*a_Position; " + // gl_Position is a special variable used to store the
                                 // final position.
                                 // Multiply the vertex by the matrix to get
                                 // the final point in
            "} ";                // normalized screen coordinates.

      final String fragmentShader = 
            "precision mediump float;" + // Set the default precision to medium.
            "varying vec4 v_Color;" + // This is the color from the vertex
                                  // shader interpolated across the triangle per fragment.
            "void main()" + // The entry point for our fragment shader.
            "{" + 
               " gl_FragColor = v_Color;" + // Pass the color directly
                                            // through the pipeline.
            "}";

      // Load in the vertex shader.
      int vertexShaderHandle = GLES20.glCreateShader( GLES20.GL_VERTEX_SHADER );

      if( vertexShaderHandle != 0 )
      {
         // Pass in the shader source.
         GLES20.glShaderSource( vertexShaderHandle, vertexShader );

         // Compile the shader.
         GLES20.glCompileShader( vertexShaderHandle );

         // Get the compilation status.
         final int[] compileStatus = new int[1];
         GLES20.glGetShaderiv( vertexShaderHandle, GLES20.GL_COMPILE_STATUS,
               compileStatus, 0 );

         // If the compilation failed, delete the shader.
         if( compileStatus[0] == 0 )
         {
            GLES20.glDeleteShader( vertexShaderHandle );
            vertexShaderHandle = 0;
         }
      }

      if( vertexShaderHandle == 0 )
      {
         throw new RuntimeException( "Error creating vertex shader." );
      }

      // Load in the fragment shader shader.
      int fragmentShaderHandle = GLES20
            .glCreateShader( GLES20.GL_FRAGMENT_SHADER );

      if( fragmentShaderHandle != 0 )
      {
         // Pass in the shader source.
         GLES20.glShaderSource( fragmentShaderHandle, fragmentShader );

         // Compile the shader.
         GLES20.glCompileShader( fragmentShaderHandle );

         // Get the compilation status.
         final int[] compileStatus = new int[1];
         GLES20.glGetShaderiv( fragmentShaderHandle, GLES20.GL_COMPILE_STATUS,
               compileStatus, 0 );

         // If the compilation failed, delete the shader.
         if( compileStatus[0] == 0 )
         {
            GLES20.glDeleteShader( fragmentShaderHandle );
            fragmentShaderHandle = 0;
         }
      }

      if( fragmentShaderHandle == 0 )
      {
         throw new RuntimeException( "Error creating fragment shader." );
      }

      // Create a program object and store the handle to it.
      theProgramHandle = GLES20.glCreateProgram();

      if( theProgramHandle != 0 )
      {
         // Bind the vertex shader to the program.
         GLES20.glAttachShader( theProgramHandle, vertexShaderHandle );

         // Bind the fragment shader to the program.
         GLES20.glAttachShader( theProgramHandle, fragmentShaderHandle );

         // Bind attributes
         GLES20.glBindAttribLocation( theProgramHandle, 0, "a_Position" );
         GLES20.glBindAttribLocation( theProgramHandle, 1, "a_Color" );

         // Link the two shaders together into a program.
         GLES20.glLinkProgram( theProgramHandle );

         // Get the link status.
         final int[] linkStatus = new int[1];
         GLES20.glGetProgramiv( theProgramHandle, GLES20.GL_LINK_STATUS,
               linkStatus, 0 );

         // If the link failed, delete the program.
         if( linkStatus[0] == 0 )
         {
            GLES20.glDeleteProgram( theProgramHandle );
            theProgramHandle = 0;
         }
      }

      if( theProgramHandle == 0 )
      {
         throw new RuntimeException( "Error creating program." );
      }

      // Set program handles. These will later be used to pass in values to the
      // program.
      theMVPMatrixHandle = GLES20.glGetUniformLocation( theProgramHandle,
            "u_MVPMatrix" );
      thePositionHandle = GLES20
            .glGetAttribLocation( theProgramHandle, "a_Position" );
      theColorHandle = GLES20.glGetAttribLocation( theProgramHandle, "a_Color" );

      // Define points for equilateral triangles.

      // This triangle is red, green, and blue.
      final float[] triangle1VerticesData =
         {
               // X, Y, Z,
               // R, G, B, A
               -0.5f, -0.25f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f,

               0.5f, -0.25f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,

               0.0f, 0.559016994f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f };

      // This triangle is yellow, cyan, and magenta.
      final float[] triangle2VerticesData =
         {
               // X, Y, Z,
               // R, G, B, A
               -0.5f, -0.25f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f,

               0.5f, -0.25f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,

               0.0f, 0.559016994f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f };

      // This triangle is white, gray, and black.
      final float[] triangle3VerticesData =
         {
               // X, Y, Z,
               // R, G, B, A
               -0.5f, -0.25f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f,

               0.5f, -0.25f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f,

               0.0f, 0.559016994f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f };

      theTriangles[0] = new Triangle( triangle1VerticesData );
      theTriangles[1] = new Triangle( triangle2VerticesData );
      theTriangles[2] = new Triangle( triangle3VerticesData );      
   }

   public void draw( float[] aViewMatrix, float[] aProjectionMatrix )
   {
      theViewMatrix = aViewMatrix;
      theProjectionMatrix = aProjectionMatrix;
      
      // Tell OpenGL to use this program when rendering.
      GLES20.glUseProgram( theProgramHandle );
      
      // Do a complete rotation every 10 seconds.
      long time = SystemClock.uptimeMillis() % 10000L;
      float angleInDegrees = (360.0f / 10000.0f) * ((int) time);
      
      // Draw the triangle facing straight on.
      Matrix.setIdentityM( mModelMatrix, 0 );
      Matrix.translateM( mModelMatrix, 0, 0.0f, 0.0f, -1.0f );
      Matrix.rotateM( mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f );
      theTriangles[0].draw();
      // drawTriangle( mTriangle1Vertices );

      // Draw one translated a bit down and rotated to be flat on the ground.
      Matrix.setIdentityM( mModelMatrix, 0 );
      Matrix.translateM( mModelMatrix, 0, 0.0f, -1.0f, -1.0f );
      Matrix.rotateM( mModelMatrix, 0, 90.0f, 1.0f, 0.0f, 0.0f );
      Matrix.rotateM( mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f );
      theTriangles[1].draw();
      // drawTriangle( mTriangle2Vertices );

      // Draw one translated a bit to the right and rotated to be facing to the
      // left.
      Matrix.setIdentityM( mModelMatrix, 0 );
      Matrix.translateM( mModelMatrix, 0, 1.0f, 0.0f, 0.0f );
      Matrix.rotateM( mModelMatrix, 0, 90.0f, 0.0f, 1.0f, 0.0f );
      Matrix.rotateM( mModelMatrix, 0, angleInDegrees, 0.0f, 0.0f, 1.0f );
      theTriangles[2].draw();
      // drawTriangle( mTriangle3Vertices );
   }

   protected class Triangle
   {
      /** Store our model data in a float buffer. */
      private final FloatBuffer theTriangleVertices;

      Triangle( float[] aVertices )
      {
         // Initialize the buffers.
         theTriangleVertices = ByteBuffer
               .allocateDirect( aVertices.length * mBytesPerFloat )
               .order( ByteOrder.nativeOrder() ).asFloatBuffer();
         theTriangleVertices.put( aVertices ).position( 0 );
      }

      public FloatBuffer getVertices()
      {
         return theTriangleVertices;
      }

      /**
       * Draws a triangle from the given vertex data.
       * 
       * @param aTriangleBuffer
       *           The buffer containing the vertex data.
       */
      public void draw()
      {
         // Pass in the position information
         theTriangleVertices.position( mPositionOffset );
         GLES20.glVertexAttribPointer( thePositionHandle, mPositionDataSize,
               GLES20.GL_FLOAT, false, mStrideBytes, theTriangleVertices );

         GLES20.glEnableVertexAttribArray( thePositionHandle );

         // Pass in the color information
         theTriangleVertices.position( mColorOffset );
         GLES20.glVertexAttribPointer( theColorHandle, mColorDataSize,
               GLES20.GL_FLOAT, false, mStrideBytes, theTriangleVertices );

         GLES20.glEnableVertexAttribArray( theColorHandle );

         float[] mMVPMatrix = new float[16];
         
         // This multiplies the view matrix by the model matrix, and stores the
         // result in the MVP matrix
         // (which currently contains model * view).
         Matrix.multiplyMM( mMVPMatrix, 0, theViewMatrix, 0, mModelMatrix, 0 );

         // This multiplies the modelview matrix by the projection matrix, and
         // stores the result in the MVP matrix
         // (which now contains model * view * projection).
         Matrix.multiplyMM( mMVPMatrix, 0, theProjectionMatrix, 0, mMVPMatrix, 0 );

         GLES20.glUniformMatrix4fv( theMVPMatrixHandle, 1, false, mMVPMatrix, 0 );
         GLES20.glDrawArrays( GLES20.GL_TRIANGLES, 0, 3 );
      }
   }

}
